package device

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"math/rand"
	"strings"
	"time"

	"device-admin/config/mysql"
	"device-admin/config/redis"
	"device-admin/internal/app/grpc/protoc/devicec"
	"device-admin/internal/app/grpc/protoc/robotc"
	"device-admin/internal/app/grpc/service/alarm_monitor"
	"device-admin/internal/app/grpc/service/pubsub"
	"device-admin/internal/app/grpc/service/terminal"
	"device-admin/internal/app/web/service/cmd"
	"device-admin/internal/app/web/service/device"
	"device-admin/internal/define"
	"device-admin/internal/models"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/go-cache"
	"192.168.1.75/go-pkg/helper"
	"192.168.1.75/go-pkg/logx"
	"google.golang.org/protobuf/proto"
)

// MsgHandler 消息处理
func MsgHandler(ss MsgStreamServer, msg *devicec.Msg, terminalType string) {
	robotID := getRobotID(ss.Context())
	rdb := redis.NewDB()
	redisCtx, redisCtxCancel := context.WithTimeout(context.Background(), time.Second*3)
	defer redisCtxCancel()
	switch msg.Name {
	case string((&devicec.Ping{}).ProtoReflect().Descriptor().FullName()):
		PingHandler(ss, msg, terminalType)
		// 升级通知响应，更新升级状态
	case string((&robotc.RspStartUpgrade{}).ProtoReflect().Descriptor().FullName()):
		rspStartUpgrade := &robotc.RspStartUpgrade{}
		proto.Unmarshal(msg.Content, rspStartUpgrade)
		if rspStartUpgrade.ErrorCode == 0 {
			terminal.UpdateUpgradeStatus(robotID, terminalType, 3) // 下载中
		} else {
			terminal.UpdateUpgradeStatus(robotID, terminalType, 2) // 暂不升级
		}
	// 请求文件传输
	case string((&robotc.ReqTransmissionFile{}).ProtoReflect().Descriptor().FullName()):
		reqTransmissionFile := &robotc.ReqTransmissionFile{}
		proto.Unmarshal(msg.GetContent(), reqTransmissionFile)
		content, totalSize, err := terminal.GetUpgradeContent(string(reqTransmissionFile.FileName), reqTransmissionFile.StartOffset, int64(reqTransmissionFile.PackageSize))
		progress := float64(0)
		if totalSize <= 0 {
			progress = 0
		} else if (reqTransmissionFile.StartOffset + int64(reqTransmissionFile.PackageSize)) <= totalSize {
			progress = float64(reqTransmissionFile.StartOffset+int64(reqTransmissionFile.PackageSize)) / float64(totalSize)
		} else {
			progress = 1
		}
		terminal.UpdateUpgradeProgress(robotID, terminalType, int(progress*100))
		terminal.SendUpgradeContent(robotID, terminalType, content, msg.Seq, err)
	// 下载结果
	case string((&robotc.ReqTransmissionEnd{}).ProtoReflect().Descriptor().FullName()):
		terminal.UpdateUpgradeStatus(robotID, terminalType, 4) // 下载结束
	// 开始升级安装
	case string((&robotc.ReqBeginUpgrade{}).ProtoReflect().Descriptor().FullName()):
		terminal.UpdateUpgradeStatus(robotID, terminalType, 8)
	// 基本信息
	case string((&devicec.BaseInfo{}).ProtoReflect().Descriptor().FullName()):
		// 注意!注意!注意!
		// 涉及到数据库操作的要随机延迟,延迟空间是60s
		// 否则，数据库崩溃
		r := rand.New(rand.NewSource(time.Now().UnixNano()))
		second := r.Intn(30)
		time.AfterFunc(time.Second*time.Duration(second), func() {
			BaseInfoHandle(ss, msg, terminalType)
		})
	// 素材同步
	case string((&devicec.ThemeUpgradeRsp{}).ProtoReflect().Descriptor().FullName()):
		response := &devicec.ThemeUpgradeRsp{}
		proto.Unmarshal(msg.Content, response)
		if response.Code == 0 { // 成功
			terminal.UpdateThemeStatus(robotID, terminalType, response.Msg, 1) // 同步中
		} else {
			terminal.UpdateThemeStatus(robotID, terminalType, response.Msg, 2) // 同步失败
		}
	// 素材同步完成
	case string((&devicec.ThemeUpgradeEndReq{}).ProtoReflect().Descriptor().FullName()):
		response := &devicec.ThemeUpgradeEndReq{}
		proto.Unmarshal(msg.Content, response)
		terminal.UpdateThemeStatus(robotID, terminalType, response.Msg, 3) // 同步成功
	// 素材同步心跳
	case string((&devicec.ThemeUpgradingHeartBeat{}).ProtoReflect().Descriptor().FullName()):
		cache.HSet(
			define.CacheRobotThemeUpgradeRTInfo,
			robotID+":"+terminalType,
			1,
			10*time.Second,
			func(_ string, field string, _ interface{}) {
				// 判断是否有同步中的素材，将其置为默认状态
				robotInfo := strings.Split(field, ":")
				mysql.NewDB().Model(&models.MaterialThemeTerminals{}).
					Where("robot_id = ? and terminal_type = ?", robotInfo[0], robotInfo[1]).
					Where("upgrade_status = 1").
					Updates(map[string]interface{}{
						"upgrade_status": 0,
						"updated_at":     time.Now().Unix(),
					})
			})
	// 广告系统素材同步
	case string((&devicec.AdvertisingPublishRsp{}).ProtoReflect().Descriptor().FullName()):
		response := &devicec.AdvertisingPublishRsp{}
		proto.Unmarshal(msg.Content, response)
		if response.Code == 0 { // 成功
			terminal.AdvertisingUpdateStatus(robotID, terminalType, response.Msg, 1) // 同步中
		} else {
			terminal.AdvertisingUpdateStatus(robotID, terminalType, response.Msg, 2) // 同步失败
		}
	// 广告系统素材同步完成
	case string((&devicec.AdvertisingPublishEndReq{}).ProtoReflect().Descriptor().FullName()):
		response := &devicec.AdvertisingPublishEndReq{}
		proto.Unmarshal(msg.Content, response)
		if response.Code == 0 { // 成功
			terminal.AdvertisingUpdateStatus(robotID, terminalType, response.Msg, 3) // 同步成功
		} else {
			terminal.AdvertisingUpdateStatus(robotID, terminalType, response.Msg, 2) // 同步失败
		}
	// 广告系统素材同步心跳
	case string((&devicec.AdvertisingPublishingHeartBeat{}).ProtoReflect().Descriptor().FullName()):
		cache.HSet(
			define.CacheRobotAdvertisingPublishRTInfo,
			robotID+":"+terminalType,
			1,
			10*time.Second,
			func(_ string, field string, _ interface{}) {
				// 判断是否有同步中的素材，将其置为默认状态
				robotInfo := strings.Split(field, ":")
				terminal.AdvertisingDeviceOffline(robotInfo[0], robotInfo[1])
			})
	// 广告请求文件传输
	case string((&devicec.AdvertisingTransmissionFileReq{}).ProtoReflect().Descriptor().FullName()):
		reqTransmissionFile := &devicec.AdvertisingTransmissionFileReq{}
		proto.Unmarshal(msg.GetContent(), reqTransmissionFile)
		content, totalSize, err := terminal.GetUpgradeContent(string(reqTransmissionFile.FileName), reqTransmissionFile.StartOffset, int64(reqTransmissionFile.PackageSize))
		terminal.SendAdvertisingFileContent(robotID, terminalType, content, totalSize, msg.Seq, err)
	// 设备上报播放次数
	case string((&devicec.AdvertisingShowCountReq{}).ProtoReflect().Descriptor().FullName()):
		req := &devicec.AdvertisingShowCountReq{}
		proto.Unmarshal(msg.GetContent(), req)
		terminal.AdvertisingShowCount(robotID, terminalType, req.ScheduleId, req.Count)
	// 广告播放清单上报
	case string((&devicec.AdvertisingPlayListReq{}).ProtoReflect().Descriptor().FullName()):
		response := &devicec.AdvertisingPlayListReq{}
		proto.Unmarshal(msg.Content, response)
		terminal.UploadAdvertisingPlayList(robotID, terminalType, response.Materials)
	// 设备配置上报
	case string((&devicec.Config97{}).ProtoReflect().Descriptor().FullName()):
		req := &devicec.Config97{}
		proto.Unmarshal(msg.GetContent(), req)
		// 注意!注意!注意!
		// 涉及到数据库操作的要随机延迟,延迟空间是60s
		// 否则，数据库崩溃
		r := rand.New(rand.NewSource(time.Now().UnixNano()))
		second := r.Intn(30)
		time.AfterFunc(time.Second*time.Duration(second), func() {
			device.TerminalConfig{}.CreateConfigs97(context.Background(), robotID, req)
		})
	// 请求通话
	case string((&devicec.StartChatReq{}).ProtoReflect().Descriptor().FullName()):
		chatData := define.ChatData{}
		chatData.Type = define.ChatType_StartChatReq
		data, _ := json.Marshal(chatData)
		rdb.Publish(redisCtx, define.RedisRobotChatData+robotID, data)
	// 通话文本内容
	case string((&devicec.ChatContent{}).ProtoReflect().Descriptor().FullName()):
		chatContent := &devicec.ChatContent{}
		proto.Unmarshal(msg.GetContent(), chatContent)
		chatData := define.ChatData{}
		chatData.Type = define.ChatType_ChatContent
		chatData.Text = chatContent.Text
		data, _ := json.Marshal(chatData)
		rdb.Publish(redisCtx, define.RedisRobotChatData+robotID, data)
	// 结束通话
	case string((&devicec.QuitChatReq{}).ProtoReflect().Descriptor().FullName()):
		chatData := define.ChatData{}
		chatData.Type = define.ChatType_ChatContent
		data, _ := json.Marshal(chatData)
		rdb.Publish(redisCtx, define.RedisRobotChatData+robotID, data)
	// 更新售卖状态
	case string((&devicec.SaleStatusRsp{}).ProtoReflect().Descriptor().FullName()):
		saleStatusRsp := &devicec.SaleStatusRsp{}
		proto.Unmarshal(msg.GetContent(), saleStatusRsp)
		SaveSaleStatus(robotID, int(saleStatusRsp.Status))
		// 告警信息
	case string((&devicec.AlarmReq{}).ProtoReflect().Descriptor().FullName()):
		alaramReq := &devicec.AlarmReq{}
		proto.Unmarshal(msg.GetContent(), alaramReq)
		alarm_monitor.AlarmMonitor{}.AddMonitor(robotID, int(alaramReq.AlarmType), time.Now().Unix(), -1, false, alaramReq.Content, "")
	}
}

// MsgPack 设备控制板命令封装
// robotID，设备id
// name,消息名称（含包名）
// content,消息内容（proto的序列化数据）
func MsgPack(robotID string, name string, content []byte, seq, terminalTyp string) (msgID string, err error) {
	if robotID == "" {
		return "", errorx.New("设备id不能为空", -1)
	}
	msg := &devicec.Msg{}
	msg.Name = name
	msg.Content = content
	if seq == "" {
		seq = helper.RandString(16)
	}
	msg.Seq = seq
	// protobuf序列化
	bytes, err := proto.Marshal(msg)
	if err != nil {
		logx.Error(context.Background(), "CMM"+terminalTyp+"Pack-Err", logx.Err(err))
		return "", err
	}
	// 通过redis发布指令，待连接会话订阅
	logx.Info(context.Background(), "device-cmd-"+name, logx.String("robot_id", robotID))
	err = pubsub.CMDPublish(robotID, terminalTyp, "msg", base64.StdEncoding.EncodeToString(bytes))
	if err != nil {
		return msg.Seq, errorx.New("命令发布失败", -1)
	}
	return msg.Seq, nil
}

// AckMsgPack ackmsg pack
func AckMsgPack(msg *devicec.Msg) *devicec.Msg {
	if msg.Ack == 0 {
		sendMsg := &devicec.Msg{}
		sendMsg.Ack = 1
		sendMsg.Name = msg.Name
		sendMsg.Seq = msg.Seq
		return sendMsg
	}
	return nil
}

// 消息回复
func PublishCMDRsp(ss MsgStreamServer, msg *devicec.Msg, terminalType string) {
	if strings.HasPrefix(msg.Seq, "waitmsg_") {
		pubsub.PublishCMDRsp(getRobotID(ss.Context()), terminalType, "msg", msg.Seq, msg.GetContent())
	}
}

// 97Ping 回复
func PingHandler(ss MsgStreamServer, msg *devicec.Msg, terminalType string) {
	robotID := getRobotID(ss.Context())
	if msg.Ack == 1 {
		cache.Expire(define.CacheRobotRTInfo+robotID+":"+terminalType, time.Second*30)
		terminal.PushToActiveDeviceList(robotID, terminalType)
	}
}

// BaseInfoHandle 基本信息上报处理
func BaseInfoHandle(ss MsgStreamServer, msg *devicec.Msg, terminalType string) {
	robotID := getRobotID(ss.Context())
	baseInfo := &devicec.BaseInfo{}
	proto.Unmarshal(msg.Content, baseInfo)
	{
		terminal.UpdateTerminalVersion(robotID, terminalType, baseInfo.Version)
		terminal.UpdateTerminalUpgradeResult(robotID, terminalType, baseInfo.Version)
	}
}

// GetLogList 获取日志列表
func GetLogList(ctx context.Context, robotID, terminalType string, time int64) (list []string, err error) {
	if robotID == "" {
		return []string{}, errorx.New("设备id不能为空", -1)
	}
	logListReq := &devicec.LogListReq{}
	logListReq.Time = time
	name := string(logListReq.ProtoReflect().Descriptor().FullName())
	content, _ := proto.Marshal(logListReq)
	seq := "waitmsg_" + helper.RandString(16)
	_, err = MsgPack(robotID, name, content, seq, terminalType)
	if err != nil {
		return []string{}, err
	}
	// 等待执行的结果
	resData, err := cmd.WaitCmdRsp(robotID, "msg", seq, terminalType)
	if err != nil {
		return []string{}, err
	}
	logListRsp := &devicec.LogListRsp{}
	proto.Unmarshal(resData, logListRsp)
	return logListRsp.GetList(), nil
}

// GetLogContent 获取日志内容
func GetLogContent(ctx context.Context, robotID, terminalType string, logFileName string) (fileContent []byte, err error) {
	if robotID == "" {
		return []byte{}, errorx.New("设备id不能为空", -1)
	}
	totalLen := int64(0)
	downloadSize := int64(-1)
	for downloadSize < totalLen {
		logContentReq := &devicec.LogContentReq{}
		logContentReq.FileName = logFileName
		// 首次默认文件偏移量和位置
		if totalLen == 0 {
			logContentReq.Offset = 0
			logContentReq.Length = 100 * 1024
		} else {
			logContentReq.Offset = downloadSize
			if totalLen-downloadSize > 100*1024 {
				logContentReq.Length = 100 * 1024
			} else {
				logContentReq.Length = totalLen - downloadSize
			}
		}
		name := string(logContentReq.ProtoReflect().Descriptor().FullName())
		content, _ := proto.Marshal(logContentReq)
		seq := "waitmsg_" + helper.RandString(16)
		_, err = MsgPack(robotID, name, content, seq, terminalType)
		if err != nil {
			return []byte{}, err
		}
		// 等待执行的结果
		resData, err := cmd.WaitCmdRsp(robotID, "msg", seq, terminalType)
		if err != nil {
			return []byte{}, err
		}
		logContentRsp := &devicec.LogContentRsp{}
		proto.Unmarshal(resData, logContentRsp)
		if totalLen == 0 {
			totalLen = logContentRsp.GetTotalSize()
		}
		if len(logContentRsp.Content) == 0 {
			return fileContent, nil
		}
		downloadSize += int64(len(logContentRsp.Content))
		fileContent = append(fileContent, logContentRsp.Content...)
	}
	return fileContent, nil
}
